iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
自我挑戰組

JS 加強筆記系列 第 12

Day 12:錯誤處理實例

  • 分享至 

  • xImage
  •  

前幾天看到 stack overflow 上的這個問題,感覺哪天莫名迷惘可能也會有相同疑問,特別記錄一下。

第一段程式碼:

const p1 = () => {
    return new Promise((resolve, reject) => {
        console.log("P1");
        resolve();
    });
};

const p2 = () => {
    return new Promise((resolve, reject) => {
        console.log("P2");
        reject();
    });
};

const p3 = () => {
    return new Promise((resolve, reject) => {
        console.log("P3");
        resolve();
    });
};

p1().catch(() => {
    console.log("Caught p1");
}).then(p2).catch(() => {
    console.log("Caught p2");
}).then(p3).catch(() => {
    console.log("Caught p3");
}).then(() => {
    console.log("Final then");
});

// 以上執行後會印出:
P1
P2
Caught p2
P3
Final then

第二段程式碼:

Promise.resolve().then(() => {
    console.log("resolve #1");
    return Promise.reject();
}).then(() => {
    console.log("resolve #2");
    return Promise.resolve();
}).then(() => {
    console.log("resolve #3");
    return Promise.resolve();
}).then(() => {
    console.log("Final end");
}).catch(() => {
    console.log("Caught");
});

// 以上會印出
resolve #1
Caught

問題:為什麼第一段在抓到 Caught p2 的錯誤後,後面的 then 還會繼續執行,而不是像第二段一樣,錯誤出現後面的程式碼就都不會做?

recovery and rethrowing

如果把第一段程式碼寫成同步搭配 try...catch 的版本,大致如下,會印出一樣的結果:

const f1 = () => {
  console.log("F1");
};

const f2 = () => {
  console.log("F2");
  throw new Error();
};

const f3 = () => {
  console.log("F3");
};

try {
  f1();
} catch {
  console.log("Caught f1");
}

try {
  f2();
} catch {
  console.log("Caught f2");
}

try {
  f3();
} catch {
  console.log("Caught f3");
}

console.log("Final code");

這樣除了比較看得清楚哪裡執行和出錯之外,還可以看出錯誤處理的用意。 try...catch 的想法是接到錯誤後,就不會讓它冒到全域,然後讓程式可以有一個修補的機會。也就是錯誤處理不但不會影響本來想執行的事,甚至是因為有它程式才能出錯而不中斷。

catch 中可以做的事是分析錯誤的種類或訊息 (例如使用 instanceof 過濾錯誤類型),然後有針對性的除錯。如果超出處理範圍,就再次拋出錯誤。例如以下的例子只處理 ReferenceError(調用了不存在的變數),其餘的再次拋出:

try {
    user = { /*...*/ };
} catch (err) {
    if (err instanceof ReferenceError) {
        alert('ReferenceError');
    } else {
        throw err; // rethrow (*)
    }
}

同理,promise 的 catch() 也有一樣作用:讓程式在錯誤時可以採取措施、後面事情還可以往下做。也因此回到原 po 的問題,catch 執行並不等於中斷程式碼,而且呼叫 catchthen 一樣會回傳 promise,讓後面可以繼續鏈接。

反過來說,如果要中斷 promise 鏈,就像在 try...catch,可以在接到錯誤後再次明確拋出錯誤或回傳一個 rejected promise,讓更後面的函式處理或通知出現不可修復的錯誤。

而原 po 的第二段程式碼之所以會出錯後就直接到最後的 catch,是因為沒有別的更早的處理錯誤。寫成同步版大致如下:

try {
    console.log("log #1");
    throw new Error();
    console.log("log #2");
    console.log("log #3");
    console.log("Final end");
} catch {
    console.log("Caught");
}

道理都很簡單,但覺得原問題的這個回答很棒,不只是討論順序或哪一行有沒有執行,還對於錯誤處理的整體概念提供了解釋。


上一篇
Day 11:錯誤處理
下一篇
Day 13:題外推書《Blood, Sweat, and Pixels》
系列文
JS 加強筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言